package com.itheima.appserver.service;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.dubbo.config.annotation.Reference;
import com.github.tobato.fastdfs.domain.conn.FdfsWebServer;
import com.github.tobato.fastdfs.domain.fdfs.StorePath;
import com.github.tobato.fastdfs.service.FastFileStorageClient;
import com.itheima.appserver.excepiton.BusinessException;
import com.itheima.appserver.excepiton.ErrorResult;
import com.itheima.appserver.interceptor.UserThreadLocal;
import com.itheima.commons.constants.Constants;
import com.itheima.commons.pojo.mongo.Comment;
import com.itheima.commons.pojo.mongo.FollowUser;
import com.itheima.commons.pojo.domain.UserInfo;
import com.itheima.commons.pojo.mongo.Video;
import com.itheima.commons.vo.reception.CommentVo;
import com.itheima.commons.vo.reception.PageResult;
import com.itheima.commons.vo.reception.VideoVo;
import com.itheima.dubbo.interfaces.CommentApi;
import com.itheima.dubbo.interfaces.UserInfoApi;
import com.itheima.dubbo.interfaces.VideoApi;
import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

/**
 * @author yaohu
 * @date 2021/12/10
 * @Version 1.0
 * @describe
 */
@Service
public class VideoService {

    @Reference
    private VideoApi videoApi;

    @Reference
    private UserInfoApi userInfoApi;

    @Reference
    private CommentApi commentApi;

    @Autowired
    private CommentsService commentsService;

    @Autowired
    private IdService idService;

    @Autowired
    private FastFileStorageClient storageClient;

    @Autowired
    private FdfsWebServer fdfsWebServer;

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 上传小视频
     *
     * @param picFile   封面图片 必须为jpg jpeg png gif 大小小于2M
     * @param videoFile 小视频 必须MP4 avi 大小小于2G
     */
    public void saveVideo(MultipartFile picFile, MultipartFile videoFile) {

        // 判断传入参数是否为空 若为空报异常
        if (ObjectUtil.isNull(picFile) || ObjectUtil.isNull(videoFile)) {
            throw new BusinessException(ErrorResult.videoOrPictureIsEmpty());
        }

        // 判断封面图片、小视频的格式
        String picSuffix = StrUtil.subAfter(picFile.getOriginalFilename(), ".", true);
        String videoSuffix = StrUtil.subAfter(videoFile.getOriginalFilename(), ".", true);

        // 封面图片 jpg jpeg png gif
        // 小视频 mp4 avi
        if (!StrUtil.equalsAny(picSuffix, true, "jpg", "png", "jpeg", "gif") ||
                !StrUtil.equalsAny(videoSuffix, true, "mp4", "avi")) {
            throw new BusinessException(ErrorResult.fileFormatError());
        }

        // 判断封面图片、小视频的大小
        if (picFile.getSize() / 1024 / 1024 > 2 || videoFile.getSize() / 1024 / 1024 / 1024 > 2) {
            throw new BusinessException(ErrorResult.videoSizeError());
        }

        // 将封面图片、小视频上传到FastDFS中
        try {
            StorePath picPath = storageClient.uploadFile(picFile.getInputStream(), picFile.getSize(), picSuffix, null);
            StorePath videoPath = storageClient.uploadFile(videoFile.getInputStream(), videoFile.getSize(), videoSuffix, null);

            Video video = new Video();
            // 主键id
            video.setId(new ObjectId());
            // 大数据推荐ID
            video.setVid(idService.createId("VIDEO"));
            // 小视频上传人ID
            video.setUserId(UserThreadLocal.getUserId());
            // 小视频配文
            video.setText("用不上");
            // 封面图片地址
            video.setPicUrl(fdfsWebServer.getWebServerUrl() + picPath.getFullPath());
            // 小视频地址
            video.setVideoUrl(fdfsWebServer.getWebServerUrl() + videoPath.getFullPath());
            // 上传时间
            video.setCreated(System.currentTimeMillis());

            videoApi.saveVideo(video);
        } catch (IOException e) {
            throw new BusinessException(ErrorResult.smallVideoUploadError());
        }
    }

    /**
     * 查询小视频列表
     *
     * @param page
     * @param pageSize
     * @return
     */
    public PageResult queryVideoList(Integer page, Integer pageSize) {

        // 从redis中查看推荐的VID
        String vids = (String) redisTemplate.opsForValue().get(Constants.VIDEOS_RECOMMEND + UserThreadLocal.getUserId());

        List<Video> videoList = Collections.emptyList();
        // 手动分页(逻辑分页) 大数据推荐系统每隔几分钟计算更新一次推荐VID
        if (StrUtil.isBlank(vids)) {
            // 随机抽取mongodb中的小视频信息
            videoList = videoApi.randomVideos(pageSize);
        } else {
            // 查出的VID是一个字符串，以,分割
            List<String> vidList = StrUtil.split(vids, ",");

            // 计算开始角标  结束角标  结束角标是否>总的数据长度
            // 1页   0     5
            // 2页   5     10
            // 3页   10    15
            // 12条数据  11
            int startIndex = (page - 1) * pageSize;
            // 防止索引越界
            int endIndex = Math.min(startIndex + pageSize, vidList.size());
            // 从vidList中分页截取
            List<String> videoIdList = CollUtil.sub(vidList, startIndex, endIndex);
            // 根据videoIdList查询小视频信息
            videoList = videoApi.findVideoByVids(Convert.toList(Long.class, videoIdList));
        }
        // 查询发布人的信息
        List<Long> userIdList = CollUtil.getFieldValues(videoList, "userId", Long.class);
        Map<Long, UserInfo> userInfoMap = userInfoApi.findUserInfoByUserIds(userIdList);

        // 合并返回VO
        ArrayList<VideoVo> videoVoList = videoVODataFormat(videoList, userInfoMap);
        return new PageResult(page, pageSize, 0L, videoVoList);
    }

    /**
     * 合并展示小视频VO信息
     *
     * @param videoList   小视频集合
     * @param userInfoMap 每个小视频发布人集合
     * @return 小视频VO信息
     */
    private ArrayList<VideoVo> videoVODataFormat(List<Video> videoList, Map<Long, UserInfo> userInfoMap) {

        ArrayList<VideoVo> videosVoList = new ArrayList<>();

        for (Video video : videoList) {
            VideoVo videoVo = new VideoVo();
            // 小视频表 小视频id
            videoVo.setId(video.getId().toHexString());
            // 小视频发布人id
            videoVo.setUserId(video.getUserId());
            // 小视频封面地址
            videoVo.setCover(video.getPicUrl());
            // 小视频地址
            videoVo.setVideoUrl(video.getVideoUrl());
            // 小视频签名
            videoVo.setSignature("");

            UserInfo userInfo = userInfoMap.get(video.getUserId());
            // 发布人头像
            videoVo.setAvatar(userInfo.getLogo());
            // 发布人昵称
            videoVo.setNickname(userInfo.getNickName());

            // 点赞数
            Integer likeCount = Convert.toInt(redisTemplate.opsForHash().get(Constants.VIDEOS_INTERACT_KEY + video.getId(), Constants.VIDEO_LIKE_HASHKEY), 0);
            // 说明当前这个小视频没有人点赞 或者有人点赞但是Redis中没值
            if (ObjectUtil.isNull(likeCount)) {
                // 从MongoDB中进行查询
                likeCount = Convert.toInt(commentApi.queryLikeCount(video.getId().toHexString()));
                // 把查询到数据给保存到Redis中
                redisTemplate.opsForHash().put(Constants.VIDEOS_INTERACT_KEY + video.getId(), Constants.VIDEO_LIKE_HASHKEY, likeCount.toString());
            }
            videoVo.setLikeCount(likeCount);

            // 是否点赞
            Boolean isLike = redisTemplate.opsForHash().hasKey(Constants.VIDEOS_INTERACT_KEY + video.getId(), Constants.VIDEO_ISLIKE_HASHKEY + UserThreadLocal.getUserId());
            if (!isLike) {
                // 从MongoDB中进行查询
                isLike = commentApi.queryUserIsLike(UserThreadLocal.getUserId(), video.getId().toHexString());
                if (isLike) {
                    // 把查询到数据给保存到Redis中
                    redisTemplate.opsForHash().put(Constants.VIDEOS_INTERACT_KEY + video.getId(), Constants.VIDEO_ISLIKE_HASHKEY + UserThreadLocal.getUserId(), "1");
                }
            }
            videoVo.setHasLiked(isLike ? 1 : 0);

            // 是否关注
            Boolean isFollow = redisTemplate.opsForSet().isMember(Constants.VIDEO_FOLLOW_HASHKEY + UserThreadLocal.getUserId(), UserThreadLocal.getUserId().toString());
            if (!isFollow) {
                // 从MongoDB中进行查询
                isFollow = videoApi.queryIsFollow(UserThreadLocal.getUserId(), video.getUserId());
                if (isFollow) {
                    // 把查询到数据给保存到Redis中
                    redisTemplate.opsForSet().add(Constants.VIDEO_FOLLOW_HASHKEY + UserThreadLocal.getUserId(), UserThreadLocal.getUserId().toString());
                }
            }
            videoVo.setHasFocus(isFollow ? 1 : 0);

            // 评论数
            Long commentCount = Convert.toLong(redisTemplate.opsForHash().get(Constants.VIDEOS_INTERACT_KEY + video.getId(), Constants.VIDEO_COMMENT_HASHKEY));
            if (ObjectUtil.isNull(commentCount)) {
                // 从MongoDB中进行查询
                commentCount = commentApi.queryCommentCount(video.getId().toHexString());
                // 把查询到数据给保存到Redis中
                redisTemplate.opsForHash().put(Constants.VIDEOS_INTERACT_KEY + video.getId(), Constants.VIDEO_COMMENT_HASHKEY, commentCount.toString());
            }
            videoVo.setCommentCount(Convert.toInt(commentCount));

            videosVoList.add(videoVo);
        }
        return videosVoList;
    }

    /**
     * 视频点赞
     *
     * @param videoId 视频id
     * @return
     */
    public Long likeComment(String videoId) {
        // 往MongoDB中插入一条点赞数据
        Long likeCount = commentsService.likeComment(videoId);
        // 视频点赞数保存到redis中
        redisTemplate.opsForHash().put(Constants.VIDEOS_INTERACT_KEY + videoId, Constants.VIDEO_LIKE_HASHKEY, likeCount.toString());
        // 保存是否点赞
        redisTemplate.opsForHash().put(Constants.VIDEOS_INTERACT_KEY + videoId, Constants.VIDEO_ISLIKE_HASHKEY + UserThreadLocal.getUserId(), "1");
        return likeCount;
    }

    /**
     * 取消视频点赞
     *
     * @param videoId
     * @return
     */
    public Long disLikeComment(String videoId) {
        Long disLikeCount = commentApi.disLikeComment(UserThreadLocal.getUserId(), videoId);
        // 把当前这个小视频的点赞数给保存到redis中
        redisTemplate.opsForHash().put(Constants.VIDEOS_INTERACT_KEY + videoId, Constants.VIDEO_LIKE_HASHKEY, disLikeCount.toString());
        // 把自己对这个小视频点赞的标记存储到redis中
        redisTemplate.opsForHash().delete(Constants.VIDEOS_INTERACT_KEY + videoId, Constants.VIDEO_ISLIKE_HASHKEY + UserThreadLocal.getUserId());
        return disLikeCount;
    }

    /**
     * 查询视频评论列表
     *
     * @param videoId
     * @param page
     * @param pageSize
     * @return
     */
    public PageResult queryCommentList(String videoId, Integer page, Integer pageSize) {
        PageResult pageResult = commentApi.queryCommentList(videoId, page, pageSize);
        if (CollUtil.isEmpty(pageResult.getItems())) {
            return pageResult;
        }
        // 从评论列表中提取发布评论人的ID
        List<Long> userIds = CollUtil.getFieldValues(pageResult.getItems(), "userId", Long.class);
        // 根据这个发布人的id集合去查询tb_user_info
        Map<Long, UserInfo> userInfoList = userInfoApi.findUserInfoByUserIds(userIds);

        List<CommentVo> voList = new ArrayList<>();
        for (Object item : pageResult.getItems()) {
            Comment comment = (Comment) item;
            // 拼凑vo对象
            CommentVo commentVo = new CommentVo();
            // 评论的id
            commentVo.setId(comment.getId().toHexString());
            // 评论的内容
            commentVo.setContent(comment.getContent());
            // 评论的时间
            commentVo.setCreateDate(DateUtil.date(comment.getCreated()).toString("HH:mm"));

            // 根据评论发布人的id找到发布人的详情
            UserInfo userInfo = userInfoList.get(comment.getUserId());
            // 发布评论人的头像
            commentVo.setAvatar(userInfo.getLogo());
            // 发布评论人的昵称
            commentVo.setNickname(userInfo.getNickName());

            // 评论的点赞数量
            Long likeCount = Convert.toLong(redisTemplate.opsForHash().get(Constants.VIDEOS_INTERACT_KEY + comment.getId(), Constants.VIDEO_LIKE_HASHKEY));
            // 说明当前这个小视频没有人点赞 或者有人点赞但是Redis中没值
            if (ObjectUtil.isNull(likeCount)) {
                // 从MongoDB中进行查询
                likeCount = commentApi.queryLikeCount(comment.getId().toHexString());
                // 把查询到数据给保存到Redis中
                redisTemplate.opsForHash().put(Constants.VIDEOS_INTERACT_KEY + comment.getId(), Constants.VIDEO_LIKE_HASHKEY, likeCount.toString());
            }
            commentVo.setLikeCount(Convert.toInt(likeCount));

            // 是否点赞
            Boolean isLike = redisTemplate.opsForHash().hasKey(Constants.VIDEOS_INTERACT_KEY, Constants.VIDEO_ISLIKE_HASHKEY + UserThreadLocal.getUserId());
            if (!isLike) {
                // 从MongoDB中进行查询
                isLike = commentApi.queryUserIsLike(UserThreadLocal.getUserId(), comment.getId().toHexString());
                if (isLike) {
                    // 把查询到数据给保存到Redis中
                    redisTemplate.opsForHash().put(Constants.VIDEOS_INTERACT_KEY + comment.getId(), Constants.VIDEO_ISLIKE_HASHKEY + UserThreadLocal.getUserId(), "1");
                }
            }
            // 当前登录用户是否对该评论点赞
            commentVo.setHasLiked(isLike ? 1 : 0);
            voList.add(commentVo);
        }
        pageResult.setItems(voList);
        return pageResult;
    }

    /**
     * 发表小视频评论
     *
     * @param videoId
     * @param content
     */
    public void saveComment(String videoId, String content) {
        // 调用Dubbo实现一个评论
        Comment comment = new Comment();
        // 评论ID
        comment.setId(new ObjectId());
        // 小视频ID
        comment.setPublishId(new ObjectId(videoId));
        // 评论类型
        comment.setCommentType(2);
        // 评论的文本
        comment.setContent(content);
        // 评论人的ID
        comment.setUserId(UserThreadLocal.getUserId());
        // 发布小视频的人的ID
        // 需要根据小视频的id去查询一个小视频详情 从中获取发布人的id
        Video video = videoApi.queryVideoById(videoId);
        if (ObjectUtil.isNotNull(video)) {
            comment.setPublishUserId(video.getUserId());
        } else {
            // publishId有可能是评论表中的ID 也有可能是 小视频中的ID
            // 根据这个publishId去查询评论表
            Comment commentId = commentApi.queryCommentById(videoId);
            comment.setPublishUserId(commentId.getUserId());
        }

        // 评论树 该功能不实现
        comment.setIsParent(false);
        comment.setParentId(null);
        comment.setCreated(System.currentTimeMillis());

        Long commentCount = commentApi.saveComment(comment);
        // 把当前这个小视频的点赞数给保存到redis中
        redisTemplate.opsForHash().put(Constants.VIDEOS_INTERACT_KEY + videoId, Constants.VIDEO_COMMENT_HASHKEY, commentCount.toString());
    }

    /**
     * 关注主播
     *
     * @param userId
     */
    public void followUser(Long userId) {
        FollowUser followUser = new FollowUser();
        followUser.setId(new ObjectId());
        followUser.setUserId(UserThreadLocal.getUserId());
        // 传递过来的userId是主播的ID
        followUser.setFollowUserId(userId);
        followUser.setCreated(System.currentTimeMillis());

        videoApi.followUser(followUser);

        // 把关注的人的id缓存到Redis中
        redisTemplate.opsForSet().add(Constants.VIDEO_FOLLOW_HASHKEY + UserThreadLocal.getUserId(), userId.toString());
    }

    /**
     * 取消关注
     *
     * @param userId
     */
    public void disFollowUser(Long userId) {
        videoApi.disFollowUser(UserThreadLocal.getUserId(), userId);
        // 从redis中删除取消关注的人
        redisTemplate.opsForSet().remove(Constants.VIDEO_FOLLOW_HASHKEY + UserThreadLocal.getUserId(), userId.toString());
    }
}
